package com.zhuhjay.security.filter;

import cn.hutool.core.lang.Assert;
import cn.hutool.extra.servlet.ServletUtil;
import com.zhuhjay.model.dto.UserLoginDto;
import com.zhuhjay.security.LoginUserDetail;
import com.zhuhjay.security.exception.UnameOrPwdNotFoundException;
import com.zhuhjay.utils.JacksonUtils;
import com.zhuhjay.utils.JwtUtils;
import com.zhuhjay.utils.ServletUtils;
import com.zhuhjay.utils.SpringContextHolder;
import com.zhuhjay.utils.result.ResultBean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static com.zhuhjay.constant.RedisConstant.TOKEN_PREFIX;
import static com.zhuhjay.constant.RedisConstant.UID_PREFIX;
import static com.zhuhjay.constant.SecurityConstant.AUTH_HEADER_KEY;
import static com.zhuhjay.utils.JwtUtils.*;

/**
 * 认证操作全靠这个过滤器，默认匹配URL为/login且必须为POST请求。
 * @author ZhuHJay
 * @date 2023/1/11 15:36
 */
public class LoginTokenFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;
    private final StringRedisTemplate redisTemplate;

    {
        redisTemplate = SpringContextHolder.getBean(StringRedisTemplate.class);
    }

    public LoginTokenFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        // 只允许使用 POST 请求进行登录校验
        this.setPostOnly(true);
        // 修改请求路径
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/user/login", "POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String body = ServletUtil.getBody(request);
        UserLoginDto user = JacksonUtils.readValue(body, UserLoginDto.class);
        try {
            Assert.notNull(user, () -> new UnameOrPwdNotFoundException("用户名或密码不能为空"));
            Assert.notBlank(user.getUsername(), () -> new UnameOrPwdNotFoundException("用户名或密码不能为空"));
            Assert.notBlank(user.getPassword(), () -> new UnameOrPwdNotFoundException("用户名或密码不能为空"));
            // 交给管理器对象进行用户表单信息的校验
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            user.getUsername(),
                            user.getPassword()
                    )
            );
        } catch (InternalAuthenticationServiceException | UnameOrPwdNotFoundException e) {
            // 抛出 AuthenticationException 异常让 unsuccessfulAuthentication 进行处理
            throw new AuthenticationException(e.getMessage()){};
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        // 获取当前登录的用户信息
        LoginUserDetail loginUserDetail = (LoginUserDetail) authResult.getPrincipal();
        // 生成token信息
        String token = JwtUtils.createToken(loginUserDetail);
        // 将 token -> uid 放置缓存中
        redisTemplate.opsForValue().set(TOKEN_PREFIX + token, String.valueOf(loginUserDetail.getUid()),
                EXPIRATION_TIME, TIME_UNIT);
        // 将 uid -> userTokenInfo 放置缓存中
        redisTemplate.opsForValue().set(UID_PREFIX + loginUserDetail.getUid(),
                JacksonUtils.writeValueAsString(loginUserDetail), EXPIRATION_TIME, TIME_UNIT);
        // 返回token
        response.setHeader(AUTH_HEADER_KEY, takeRespAuth(token));
        ServletUtils.writeToJson(response, ResultBean.ok("登录成功"));
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        ServletUtils.writeToJson(response, ResultBean.fail(failed.getMessage()));
    }
}
